#===============================================================================
# The MIT License 
#
# Copyright (c) 2008 VendAsta Technologies Inc.
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#===============================================================================
""" Python wrapper for the RapLeaf Person and Address Book APIs
    (http://www.rapleaf.com/developer)
    Gets the XML response from the RapLeaf server and parses it into a 
    simple dictionary.
    
    Written by Sean Lynch at VendAsta Technologies Inc. Inspired by
    the PHP Person API written by Abhishek Nagraj 
    (http://code.google.com/p/rapleaf-person-api-php-library/)

    Person API Usage: 
    1. rapleaf = Rapleaf( YOUR_API_KEY_HERE ) to initialize
    2. details = rapleaf.get_details( EMAIL_ADDRESS ) to get info from API

    Address Book API Usage: 
    1. rapleaf = Rapleaf( YOUR_API_KEY_HERE ) to initialize
    2. details = rapleaf.get_contacts( EMAIL_ADDRESS ) to get info from API
    
    July 16, 2008
"""

from urllib import quote, urlencode
from xml.etree import ElementTree as ET



class Rapleaf(object):
    """Simple class to encapsulate accessing the RapLeaf API and parsing
    the results
    """
    
    PERSON_ERRORS = { 
        200: None, 
        201: 'This person has now been created. Check back shortly and we should have data.',
        202: 'This person is currently being searched. Check back shortly and we should have data.',
        400: 'Invalid email address.',
        401: 'API key was not provided or is invalid.',
        403: 'Your query limit has been exceeded. The default limit is 1,000. Contact developer@rapleaf.com if you would like to increase your limit.',
        404: 'Only returned for lookup by hash. We do not have this email in our system and are not able to create a person using a hash. If you would like better results, consider supplying the unhashed email address.',
        500: 'There was an unexpected error on our server. This should be very rare and if you see it please contact developer@rapleaf.com.',
        }
    
    ABOOK_ERRORS = { 
        200: None, 
        400: 'The request did not contain all required parameters.',
        401: 'API key was not provided or is invalid.',
        403: 'Your query limit has been exceeded. The default limit is 1,000. Contact developer@rapleaf.com if you would like to increase your limit.',
        420: 'The login or password were incorrect.',
        500: 'There was an unexpected error on our server. This should be very rare and if you see it please contact developer@rapleaf.com.',
        520: 'There was an error while reading the contacts from the address book.',
        }
    
    DEFAULT_ERROR = 'An unexpected error was encountered.' 
    
    PERSON_URL = "http://api.rapleaf.com/v2/person/"
    ABOOK_URL = "http://api.rapleaf.com/v2/abook/"


    def __init__(self, api_key):
        """Create a RapleafPerson object with your api_key
        
        You can request an API key here: http://www.rapleaf.com/developer/api_key
        """
        
        self._api_key = api_key
        
    def get_details(self, email):
        """Request the details of a specific email account from Rapleaf
        
        A dictionary is returned with all the elements of the response
        """
        
        # assemble post_data string
        request_url = Rapleaf.PERSON_URL + quote(email)
        xml, status = self.__get_request(request_url)

        result = {
            'status': status,  
            'error':  self.__decode_person_error(status)               
        }
        
        # Don't bother trying to parse if the request failed 
        if status != 200:
            return result    
        
        # Root elements of response
        root = ET.XML(xml) 
        
        result['id'] = root.attrib['id']
        
        #Construct the major sections of the response
        result['basics'] = self.__parse_basics(root)
            
        result['memberships-primary'] = [child.attrib for child in root.findall('memberships/primary/membership')]
            
        if root.find('memberships/supplemental'):
            result['memberships-supplemental'] = [child.attrib for child in root.findall('memberships/supplemental/membership')]
                
        result['reputation'] = self.__parse_reputation(root)  
                
        return result
    
    def get_contacts(self, login, password, service=None):
        """Request the contacts of a specific account using the Rapleaf API
        
        You can optionally specify which webmail service to use. Supported 
        values are 'gmail', 'yahoo', 'hotmail', and 'aol'. 
        
        A dictionary is returned with all the elements of the response
        """
        
        # assemble post_data string
        post_vars = {'login': login,
                     'password': password}
        
        if service:
            post_vars['service'] = service
        
        xml, status = self.__post_request(Rapleaf.ABOOK_URL, post_vars)
        
        result = {
            'status': status,  
            'error':  self.__decode_addressbook_error(status)               
        }
        
        # Don't bother trying to parse if the request failed 
        if status != 200:
            return result   
        
        # Root elements of response
        root = ET.XML(xml)
        
        result['contacts'] = [contact.attrib for contact in root.findall('contact')]
        
        return result
        
    def __decode_person_error(self, status):
        """Convert any arbitrary error code into a string message"""
        if Rapleaf.PERSON_ERRORS.has_key(status):
            return Rapleaf.PERSON_ERRORS[status]
        else:
            return Rapleaf.DEFAULT_ERROR
        
    def __decode_addressbook_error(self, status):
        """Convert any arbitrary error code into a string message"""
        if Rapleaf.ABOOK_ERRORS.has_key(status):
            return Rapleaf.ABOOK_ERRORS[status]
        else:
            return Rapleaf.DEFAULT_ERROR
        
    def __parse_basics(self, root):
        """Parse the <basics> tag into a dictionary"""
        elements = ['name', 'gender', 'location', 'earliest_known_activity', 
                    'latest_known_activity', 'num_friends']
        
        
        root = root.find('basics')
        
        result = dict([(a, root.find(a).text) for a in elements if root.find(a) is not None])
        
        # Parse sub-trees
        result['universities'] = [child.text for child in root.findall('universities/university')]
        result['occupations'] = [child.attrib for child in root.findall('occupations/occupation')]
        
        return result
        
    def __parse_reputation(self, root):
        """Parse the <reputation> tag into a dictionary"""
        elements = ['score', 'commerce_score', 'percent_positive', 'profile_url']
        
        root = root.find('reputation')
        
        result = dict([ (a, root.find(a).text) for a in elements if root.find(a) is not None ])
        
        # Parse badges sub-tree
        badges = root.findall('badges/badge')
        result['badges'] = dict([ (badge.attrib['type'], badge.text) for badge in badges ])
        
        return result
    
    def __get_request(self, url):
        """Submit a GET request to the specified URL
        
        return type is a tuple: response, response_code
        """    
        
        headers = { 'Authorization': self._api_key }
        
        if USE_URLFETCH:
            result = urlfetch.fetch(url, None, urlfetch.GET, headers, False)
            return result.content, result.status_code
        else:
            try:
                req = urllib2.Request(url, None, headers)
                result = urllib2.urlopen(req)
                return result.read(), 200
            except urllib2.HTTPError, err:
                return '', err.code
    
    def __post_request(self, url, post_vars=None):
        """Submit a GET request to the specified URL
        
        return type is a tuple: response, response_code
        """    
        
        data = urlencode(post_vars)
        
        # Append API key to headers
        headers = { 'Authorization': self._api_key }
        
        if USE_URLFETCH:
            result = urlfetch.fetch(url, data, urlfetch.POST, headers, False)
            return result.content, result.status_code
        else:
            try:
                req = urllib2.Request(url, data, headers)
                result = urllib2.urlopen(req)
                return result.read(), 200
            except urllib2.HTTPError, err:
                return '', err.code
            
# Try to use Google's urlfetch (Google App Engine compatibility)
# fall back on urllib2
USE_URLFETCH = False
try:
    from google.appengine.api import urlfetch
    USE_URLFETCH = True
    
    try:
        urlfetch.fetch("", None, urlfetch.GET, {}, False)
    except AssertionError:
        # urlfetch enable code 
        # Only required when running urlfetch code outside of dev_appserver 
        from google.appengine.api import urlfetch_stub 
        from google.appengine.api import apiproxy_stub_map 
        apiproxy_stub_map.apiproxy.RegisterStub('urlfetch', urlfetch_stub.URLFetchServiceStub()) 
except ImportError:
    import urllib2
        
def _test():
    """Sanity test"""
    # Need to include your own API key and email address
    api = Rapleaf("")
    details = api.get_details("")
    print details
    
    contacts = api.get_contacts("")
    print contacts

if __name__ == '__main__' :
    _test()